#include "AVAPIs_Server.h"
#include "ProductTestIotSpkPlay.h"

#define DEF_TAG             "TutkServer"

#define LICENSE_KEY "AQAAAFUfFgp5Ozp6QFyq41arYv5IYshlKfnp3SZ0VFFjpVu5raKjT51RbU+tpCWUmiL+qeETM+ojAu7BxpQVNWvzOyCp7NvUuFId1uMSN8aunpznad/naRVJTzEQLPAx8ypgZdXH/TtHh1iTgXhg0WEIxxp9fTMCJrqNhtvpQ2SWBmFLasKLxkcQF4c6J/L5OHLrkCDZGJndYQGrb9o+e9Dr01Ab"

char gAvAccount[NEW_MAXSIZE_VIEWACC] = "admin";
char gAvPassword[NEW_MAXSIZE_VIEWPWD] = "888888";
char gIotcAuthkey[IOTC_AUTH_KEY_LENGTH+1]="00000000"; //use "00000000" as default authkey

AV_Client gClientInfo[MAX_CLIENT_NUMBER];

char gVideoFn[128] = {0};
char gAudioFn[128] = {0};

static int gProcessRun = 1;
static int gOnlineNum = 0;
extern char g_TUTKSdk_UID[DEF_COM_UUID_LEN];

static char is_push = 0;
pStSdkRingBufInfoProducer p2p_producer;

int p2p_push_message(void)
{
    is_push = 1;
    return 0;
}

/**
void *p2p_push_message_thread(void *arg)
{
    while (1)
    {
        usleep(300 * 1000);
    }
    return NULL;
}
**/

void PrintErrHandling (int nErr)
{
    switch (nErr)
    {
    case IOTC_ER_MASTER_NOT_RESPONSE :
        //-60 IOTC_ER_MASTER_NOT_RESPONSE
        printf ("[Error code : %d]\n", IOTC_ER_MASTER_NOT_RESPONSE );
        printf ("Master server doesn't respond.\n");
        printf ("Please check the network wheather it could connect to the Internet.\n");
        break;
    case IOTC_ER_SERVER_NOT_RESPONSE :
        //-1 IOTC_ER_SERVER_NOT_RESPONSE
        printf ("[Error code : %d]\n", IOTC_ER_SERVER_NOT_RESPONSE );
        printf ("P2P Server doesn't respond.\n");
        printf ("Please check the network wheather it could connect to the Internet.\n");
        break;
    case IOTC_ER_FAIL_RESOLVE_HOSTNAME :
        //-2 IOTC_ER_FAIL_RESOLVE_HOSTNAME
        printf ("[Error code : %d]\n", IOTC_ER_FAIL_RESOLVE_HOSTNAME);
        printf ("Can't resolve hostname.\n");
        break;
    case IOTC_ER_ALREADY_INITIALIZED :
        //-3 IOTC_ER_ALREADY_INITIALIZED
        printf ("[Error code : %d]\n", IOTC_ER_ALREADY_INITIALIZED);
        printf ("Already initialized.\n");
        break;
    case IOTC_ER_FAIL_CREATE_MUTEX :
        //-4 IOTC_ER_FAIL_CREATE_MUTEX
        printf ("[Error code : %d]\n", IOTC_ER_FAIL_CREATE_MUTEX);
        printf ("Can't create mutex.\n");
        break;
    case IOTC_ER_FAIL_CREATE_THREAD :
        //-5 IOTC_ER_FAIL_CREATE_THREAD
        printf ("[Error code : %d]\n", IOTC_ER_FAIL_CREATE_THREAD);
        printf ("Can't create thread.\n");
        break;
    case IOTC_ER_UNLICENSE :
        //-10 IOTC_ER_UNLICENSE
        printf ("[Error code : %d]\n", IOTC_ER_UNLICENSE);
        printf ("This UID is unlicense.\n");
        printf ("Check your UID.\n");
        break;
    case IOTC_ER_NOT_INITIALIZED :
        //-12 IOTC_ER_NOT_INITIALIZED
        printf ("[Error code : %d]\n", IOTC_ER_NOT_INITIALIZED);
        printf ("Please initialize the IOTCAPI first.\n");
        break;
    case IOTC_ER_TIMEOUT :
        //-13 IOTC_ER_TIMEOUT
        break;
    case IOTC_ER_INVALID_SID :
        //-14 IOTC_ER_INVALID_SID
        printf ("[Error code : %d]\n", IOTC_ER_INVALID_SID);
        printf ("This SID is invalid.\n");
        printf ("Please check it again.\n");
        break;
    case IOTC_ER_EXCEED_MAX_SESSION :
        //-18 IOTC_ER_EXCEED_MAX_SESSION
        printf ("[Error code : %d]\n", IOTC_ER_EXCEED_MAX_SESSION);
        printf ("[Warning]\n");
        printf ("The amount of session reach to the maximum.\n");
        printf ("It cannot be connected unless the session is released.\n");
        break;
    case IOTC_ER_CAN_NOT_FIND_DEVICE :
        //-19 IOTC_ER_CAN_NOT_FIND_DEVICE
        printf ("[Error code : %d]\n", IOTC_ER_CAN_NOT_FIND_DEVICE);
        printf ("Device didn't register on server, so we can't find device.\n");
        printf ("Please check the device again.\n");
        printf ("Retry...\n");
        break;
    case IOTC_ER_SESSION_CLOSE_BY_REMOTE :
        //-22 IOTC_ER_SESSION_CLOSE_BY_REMOTE
        printf ("[Error code : %d]\n", IOTC_ER_SESSION_CLOSE_BY_REMOTE);
        printf ("Session is closed by remote so we can't access.\n");
        printf ("Please close it or establish session again.\n");
        break;
    case IOTC_ER_REMOTE_TIMEOUT_DISCONNECT :
        //-23 IOTC_ER_REMOTE_TIMEOUT_DISCONNECT
        printf ("[Error code : %d]\n", IOTC_ER_REMOTE_TIMEOUT_DISCONNECT);
        printf ("We can't receive an acknowledgement character within a TIMEOUT.\n");
        printf ("It might that the session is disconnected by remote.\n");
        printf ("Please check the network wheather it is busy or not.\n");
        printf ("And check the device and user equipment work well.\n");
        break;
    case IOTC_ER_DEVICE_NOT_LISTENING :
        //-24 IOTC_ER_DEVICE_NOT_LISTENING
        printf ("[Error code : %d]\n", IOTC_ER_DEVICE_NOT_LISTENING);
        printf ("Device doesn't listen or the sessions of device reach to maximum.\n");
        printf ("Please release the session and check the device wheather it listen or not.\n");
        break;
    case IOTC_ER_CH_NOT_ON :
        //-26 IOTC_ER_CH_NOT_ON
        printf ("[Error code : %d]\n", IOTC_ER_CH_NOT_ON);
        printf ("Channel isn't on.\n");
        printf ("Please open it by IOTC_Session_Channel_ON() or IOTC_Session_Get_Free_Channel()\n");
        printf ("Retry...\n");
        break;
    case IOTC_ER_SESSION_NO_FREE_CHANNEL :
        //-31 IOTC_ER_SESSION_NO_FREE_CHANNEL
        printf ("[Error code : %d]\n", IOTC_ER_SESSION_NO_FREE_CHANNEL);
        printf ("All channels are occupied.\n");
        printf ("Please release some channel.\n");
        break;
    case IOTC_ER_TCP_TRAVEL_FAILED :
        //-32 IOTC_ER_TCP_TRAVEL_FAILED
        printf ("[Error code : %d]\n", IOTC_ER_TCP_TRAVEL_FAILED);
        printf ("Device can't connect to Master.\n");
        printf ("Don't let device use proxy.\n");
        printf ("Close firewall of device.\n");
        printf ("Or open device's TCP port 80, 443, 8080, 8000, 21047.\n");
        break;
    case IOTC_ER_TCP_CONNECT_TO_SERVER_FAILED :
        //-33 IOTC_ER_TCP_CONNECT_TO_SERVER_FAILED
        printf ("[Error code : %d]\n", IOTC_ER_TCP_CONNECT_TO_SERVER_FAILED);
        printf ("Device can't connect to server by TCP.\n");
        printf ("Don't let server use proxy.\n");
        printf ("Close firewall of server.\n");
        printf ("Or open server's TCP port 80, 443, 8080, 8000, 21047.\n");
        printf ("Retry...\n");
        break;
    case IOTC_ER_NO_PERMISSION :
        //-40 IOTC_ER_NO_PERMISSION
        printf ("[Error code : %d]\n", IOTC_ER_NO_PERMISSION);
        printf ("This UID's license doesn't support TCP.\n");
        break;
    case IOTC_ER_NETWORK_UNREACHABLE :
        //-41 IOTC_ER_NETWORK_UNREACHABLE
        printf ("[Error code : %d]\n", IOTC_ER_NETWORK_UNREACHABLE);
        printf ("Network is unreachable.\n");
        printf ("Please check your network.\n");
        printf ("Retry...\n");
        break;
    case IOTC_ER_FAIL_SETUP_RELAY :
        //-42 IOTC_ER_FAIL_SETUP_RELAY
        printf ("[Error code : %d]\n", IOTC_ER_FAIL_SETUP_RELAY);
        printf ("Client can't connect to a device via Lan, P2P, and Relay mode\n");
        break;
    case IOTC_ER_NOT_SUPPORT_RELAY :
        //-43 IOTC_ER_NOT_SUPPORT_RELAY
        printf ("[Error code : %d]\n", IOTC_ER_NOT_SUPPORT_RELAY);
        printf ("Server doesn't support UDP relay mode.\n");
        printf ("So client can't use UDP relay to connect to a device.\n");
        break;

    default :
        printf("[Error code : %d]\n", nErr);
        break;
    }
}

void PrintVersion(void)
{
    printf("IOTCAPI version[%s] AVAPI version[%s]\n", IOTC_Get_Version_String(), avGetAVApiVersionString());
}

void printAccount()
{
    printf("=====Account_info=======\n");
    printf("gAvAccount:%s\n", gAvAccount);
    printf("gAvPassword:%s\n", gAvPassword);
    printf("gIotcAuthkey:%s\n", gIotcAuthkey);
    printf("========================\n");
}

int ExPasswordAuthCallBackFn(const char *account, char *pwd, unsigned int pwd_buf_size)
{
    if (strcmp(account, gAvAccount) != 0)
        return -1;

    if (pwd_buf_size <= strlen(gAvPassword))
    {
        return -1;
    }
    strcpy(pwd, gAvPassword);

    return 0;
}

int ExTokenAuthCallBackFn(const char *identity, char *token, unsigned int token_buf_size)
{
    return 0;
}

int ExTokenDeleteCallBackFn(int av_index, const char *identity)
{
    return 0;
}

int ExTokenRequestCallBackFn(int av_index,const char *identity,
                             const char *identity_description,char *token,unsigned int token_length)
{
    return 0;
}

void ExGetIdentityArrayCallBackFn(int avIndex, avServSendIdentityArray send_identity_array)
{
}

int ExChangePasswordCallBackFn(int av_index,const char *account,const char *old_password,
                               const char *new_password,const char *new_iotc_authkey)
{
    if (strcmp(account, gAvAccount) != 0)
        return -1;
    if (strcmp(old_password, gAvPassword) != 0)
        return -1;

    // please make sure the maximum password size is enough
    if (strlen(new_password) > NEW_MAXSIZE_VIEWPWD)
        return -1;
    strcpy(gAvPassword, new_password);

    //auth key has been changed inside the SDK, please save it
    memcpy(gIotcAuthkey, new_iotc_authkey, IOTC_AUTH_KEY_LENGTH);

    //IOTC_Device_Update_Authkey(gIotcAuthkey);
    printAccount();
    return 0; //success
}

void ExAbilityRequestFn(int av_index,avServSendAbility send_ability)
{
    send_ability(av_index, (const unsigned char*)"NULL", 4);
}

void regedit_client_to_command(int SID, int avIndex)
{
    AV_Client *p = &gClientInfo[SID];
    p->avCmdIndex= avIndex;
}

void unregedit_client_from_command(int SID)
{
    AV_Client *p = &gClientInfo[SID];
    p->avCmdIndex = -1;
}

void regedit_client_to_video(int SID, int avIndex)
{
    AV_Client *p = &gClientInfo[SID];
    p->avIndex = avIndex;
    p->bEnableVideo = 1;
    p->bSendCache = 1;
    p->waitKeyFrame = 1;
}

void unregedit_client_from_video(int SID)
{
    AV_Client *p = &gClientInfo[SID];
    p->bEnableVideo = 0;
}

void regedit_client_to_audio(int SID, int avIndex)
{
    AV_Client *p = &gClientInfo[SID];
    p->bEnableAudio = 1;
}

void unregedit_client_from_audio(int SID)
{
    AV_Client *p = &gClientInfo[SID];
    p->bEnableAudio = 0;
}

void regedit_client_stream_mode(int SID, int streamMode)
{
    AV_Client *p = &gClientInfo[SID];
    p->bTwoWayStream = streamMode;
}

void Handle_IOCTRL_Cmd(int SID, int avIndex, char *buf, int type)
{
    PRT_INFO(DEF_TAG,"Handle CMD: 0x%04x\n",type);
    switch(type)
    {
    case IOTYPE_USER_IPCAM_START:
    {
        Ext_InComFile_Creat(DEF_FLAG_PRODUCT_TESTING);
        SMsgAVIoctrlAVStream *p = (SMsgAVIoctrlAVStream *)buf;
        printf("IOTYPE_USER_IPCAM_START, ch:%d, avIndex:%d\n\n", p->channel, avIndex);
        //get writer lock
        int lock_ret = pthread_rwlock_wrlock(&gClientInfo[SID].sLock);
        if(lock_ret)
            printf("Acquire SID %d rwlock error, ret = %d\n", SID, lock_ret);
        regedit_client_to_video(SID, avIndex);
        //release lock
        lock_ret = pthread_rwlock_unlock(&gClientInfo[SID].sLock);
        if(lock_ret)
            printf("Release SID %d rwlock error, ret = %d\n", SID, lock_ret);

        printf("regedit_client_to_video OK\n");
    }
        break;

    case IOTYPE_USER_IPCAM_STOP:
    {
        Ext_InComFile_Remove(DEF_FLAG_PRODUCT_TESTING);
        SMsgAVIoctrlAVStream *p = (SMsgAVIoctrlAVStream *)buf;
        printf("IOTYPE_USER_IPCAM_STOP, ch:%d, avIndex:%d\n\n", p->channel, avIndex);
        //get writer lock
        int lock_ret = pthread_rwlock_wrlock(&gClientInfo[SID].sLock);
        if(lock_ret)
            printf("Acquire SID %d rwlock error, ret = %d\n", SID, lock_ret);
        unregedit_client_from_video(SID);
        //release lock
        lock_ret = pthread_rwlock_unlock(&gClientInfo[SID].sLock);
        if(lock_ret)
            printf("Release SID %d rwlock error, ret = %d\n", SID, lock_ret);
        printf("unregedit_client_from_video OK\n");
    }
        break;

    case IOTYPE_USER_IPCAM_AUDIOSTART:
    {
        SMsgAVIoctrlAVStream *p = (SMsgAVIoctrlAVStream *)buf;
        printf("IOTYPE_USER_IPCAM_AUDIOSTART, ch:%d, avIndex:%d\n\n", p->channel, avIndex);
        //get writer lock
        int lock_ret = pthread_rwlock_wrlock(&gClientInfo[SID].sLock);
        if(lock_ret)
            printf("Acquire SID %d rwlock error, ret = %d\n", SID, lock_ret);
        regedit_client_to_audio(SID, avIndex);
        //release lock
        lock_ret = pthread_rwlock_unlock(&gClientInfo[SID].sLock);
        if(lock_ret)
            printf("Release SID %d rwlock error, ret = %d\n", SID, lock_ret);
        printf("regedit_client_to_audio OK\n");
    }
        break;

    case IOTYPE_USER_IPCAM_AUDIOSTOP:
    {
        SMsgAVIoctrlAVStream *p = (SMsgAVIoctrlAVStream *)buf;
        printf("IOTYPE_USER_IPCAM_AUDIOSTOP, ch:%d, avIndex:%d\n\n", p->channel, avIndex);
        //get writer lock
        int lock_ret = pthread_rwlock_wrlock(&gClientInfo[SID].sLock);
        if(lock_ret)
            printf("Acquire SID %d rwlock error, ret = %d\n", SID, lock_ret);
        unregedit_client_from_audio(SID);
        //release lock
        lock_ret = pthread_rwlock_unlock(&gClientInfo[SID].sLock);
        if(lock_ret)
            printf("Release SID %d rwlock error, ret = %d\n", SID, lock_ret);
        printf("unregedit_client_from_audio OK\n");
    }
        break;

    case IOTYPE_USER_IPCAM_GETAUDIOOUTFORMAT_REQ:
    {
        Handle_IOTYPE_USER_IPCAM_GETAUDIOOUTFORMAT_REQ(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_DEVINFO_REQ:
    {
        Handle_IOTYPE_USER_DEVINFO(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_LISTWIFIAP_REQ:
    {
        Handle_IOTYPE_USER_GET_LISTWIFIAP(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_SPEAKERSTART:
    {
        Handle_IOTYPE_USER_IPCAM_SPEAKERSTART(SID, avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_SPEAKERSTOP:
    {
        Handle_IOTYPE_USER_IPCAM_SPEAKERSTOP(SID, avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_DEVICE_RESET_REQ:
    {
        Handle_IOTYPE_USER_DEVICE_RESET(SID, avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_SEND_MANUAL_FEED_REQ:
    {
        Handle_IOTYPE_USER_FEED_COMMAND(SID, avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_GET_PRODUT_AUTOTEST_RESULT_REQ:
    {
        Handle_IOTYPE_USER_GET_PRODUT_AUTOTEST_RESULT(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_SET_PRODUT_INFO_REQ:
    {
        Handle_IOTYPE_USER_SET_PRODUCT_INFO(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_GET_DEV_MAC_REQ:
    {
        Handle_IOTYPE_USER_GET_MAC(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_GET_PID_REQ:
    {
        Handle_IOTYPE_USER_GET_PID(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_SET_PETWANT_TYPE_REQ:
    {
        Handle_IOTYPE_USER_SET_PETWANT_TYPE(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_SET_LOCATION_MODE_REQ:
    {
        Handle_IOTYPE_USER_SET_LOCATION_MODE_TYPE(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_GET_DEV_UCHECK_REQ:
    {
        Handle_IOTYPE_USER_GET_DEV_UCHECK(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_SET_DEV_PID_REQ:
    {
        Handle_IOTYPE_USER_SET_DEV_PID(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_SKIP_PRODUCT_TEST_REQ:
    {
        Handle_IOTYPE_USER_SKIP_PRODUCT_TEST(avIndex, buf);
    }
        break;

    case IOTYPE_USER_IPCAM_GET_BLE_MAC_REQ:
    {
        Handle_IOTYPE_USER_GET_BLE_MCU_MAC(avIndex, buf);
        break;
    }

    case IOTYPE_SET_DEVICE_BASIC_CONFIG_REQ:
    {
        Handle_IOTYPE_SET_DEVICE_BASIC_CONFIG(avIndex, buf);
        break;
    }

    case IOTYPE_USER_IPCAM_SET_TY_AP_NAME_REQ:
    {
        Handle_IOTYPE_SET_TY_AP_NAME(avIndex, buf);
        break;
    }

    case IOTYPE_USER_IPCAM_REPORT_UID_AUTHKEY_REQ:
    {
        Handle_IOTYPE_REPORT_UID_AUTHKEY(avIndex, buf);
        break;
    }

    case IOTYPE_PRODUCT_FUNC_MODIFIED_REQ:
    {
        Handle_IOTYPE_PRODUCT_FUNC_MODIFIED(avIndex, buf);
        break;
    }

    case IOTYPE_USER_GET_BATTERY_CAPACITY_REQ:
    {
        Handle_IOTYPE_GET_BATTERY_CAPACITY(avIndex, buf);
        break;
    }

    //control:1-上 2-下 3-左 6-右
    case IOTYPE_USER_IPCAM_PTZ_COMMAND:
    {
        printf("avIndex %d:  type[%X]\n", avIndex, type);
        SMsgAVIoctrlPtzCmd *req = (SMsgAVIoctrlPtzCmd *)buf;

        if(req->control == PTZ_CONTROL_UP)
        {
        }
        else if(req->control == PTZ_CONTROL_DOWN)
        {
        }
        else if(req->control == PTZ_CONTROL_LEFT)
        {

        }
        else if(req->control == PTZ_CONTROL_RIGHT)
        {

        }
        break;
    }

    default:
        printf("avIndex %d: non-handle type[%X]\n", avIndex, type);
        break;
    }
}

int AuthCallBackFn(char *viewAcc,char *viewPwd)
{
    if(strcmp(viewAcc, gAvAccount) == 0 && strcmp(viewPwd, gAvPassword) == 0)
        return 1;

    printf("name :%s passwd:%s .viewAcc:%s..viewpwd:%s\n", gAvAccount, gAvPassword, viewAcc, viewPwd);
    return 0;
}

/****
Thread - Start AV server and recv IOCtrl cmd for every new av idx
*/
void *thread_ForAVServerStart(void *arg)
{
    int SID = *(int *)arg;
    free(arg);
    int ret;
    unsigned int ioType;
    char ioCtrlBuf[AV_MAX_IOCTRL_DATA_SIZE];
    struct st_SInfoEx Sinfo;

    printf("SID[%d], thread_ForAVServerStart, OK\n", SID);

    AVServStartInConfig avStartInConfig;
    AVServStartOutConfig avStartOutConfig;

    memset(&avStartInConfig, 0, sizeof(AVServStartInConfig));
    avStartInConfig.cb               = sizeof(AVServStartInConfig);
    avStartInConfig.iotc_session_id  = SID;
    avStartInConfig.iotc_channel_id  = 0;
    avStartInConfig.timeout_sec      = 30;
    avStartInConfig.password_auth    = ExPasswordAuthCallBackFn;
    avStartInConfig.token_auth       = ExTokenAuthCallBackFn;
    avStartInConfig.server_type      = SERVTYPE_STREAM_SERVER;
    avStartInConfig.resend           = ENABLE_RESEND;
    avStartInConfig.token_delete     = ExTokenDeleteCallBackFn;
    avStartInConfig.token_request    = ExTokenRequestCallBackFn;
    avStartInConfig.identity_array_request = ExGetIdentityArrayCallBackFn;
    avStartInConfig.change_password_request    = ExChangePasswordCallBackFn;
    avStartInConfig.ability_request  = ExAbilityRequestFn;
    //avStartInConfig.json_request = Ex_avRecvJSONCtrlRequestFn;

    avStartInConfig.security_mode = AV_SECURITY_SIMPLE;
    avStartOutConfig.cb              = sizeof(AVServStartOutConfig);

    int avIndex = avServStartEx(&avStartInConfig, &avStartOutConfig);
    /*
    int nResend = 1;
    int avIndex = avServStart3(SID, AuthCallBackFn, 0, SERVTYPE_STREAM_SERVER, 0, &nResend);
    */

    if(avIndex < 0)
    {
        printf("avServStartEx failed!! SID[%d] code[%d]!!!\n", SID, avIndex);
        printf("thread_ForAVServerStart: exit!! SID[%d]\n", SID);
        IOTC_Session_Close(SID);
        gOnlineNum--;
        pthread_exit(0);
    }
    else
    {
        regedit_client_to_command(SID, avIndex);
    }

    Sinfo.size = sizeof(struct st_SInfoEx);
    if (IOTC_Session_Check_Ex(SID, &Sinfo) == IOTC_ER_NoERROR)
    {
        char *mode[3] = {"P2P", "RLY", "LAN"};
        // print session information(not a must)
        if (isdigit(Sinfo.RemoteIP[0]))
            printf("Client is from[IP:%s, Port:%d] Mode[%s] VPG[%d:%d:%d] VER[%X] NAT[%d] AES[%d]\n",
                   Sinfo.RemoteIP, Sinfo.RemotePort, mode[(int)Sinfo.Mode], Sinfo.VID, Sinfo.PID,
                   Sinfo.GID, Sinfo.IOTCVersion, Sinfo.RemoteNatType, Sinfo.isSecure);
    }
    printf("avServStartEx OK, avIndex[%d], resend[%d] two_way_streaming[%d]\n",
           avIndex, avStartOutConfig.resend, avStartOutConfig.two_way_streaming);
    printf("auth_type[%d] account_or_identity[%s]\n\n", avStartOutConfig.auth_type,
           avStartOutConfig.account_or_identity);
    regedit_client_stream_mode(SID, avStartOutConfig.two_way_streaming);
    avServSetResendSize(avIndex, 1024);

    while(1)
    {
        ret = avRecvIOCtrl(avIndex, &ioType, (char *)ioCtrlBuf, AV_MAX_IOCTRL_DATA_SIZE, 1000);
        if(ret >= 0)
        {
            Handle_IOCTRL_Cmd(SID, avIndex, ioCtrlBuf, ioType);
        }
        else if(ret != AV_ER_TIMEOUT)
        {
            printf("avIndex[%d], avRecvIOCtrl error, code[%d]\n",avIndex, ret);
            break;
        }
    }

    unregedit_client_from_command(SID);
    unregedit_client_from_video(SID);
    unregedit_client_from_audio(SID);
    avServStop(avIndex);
    printf("SID[%d], avIndex[%d], thread_ForAVServerStart exit!!\n", SID, avIndex);

    IOTC_Session_Close(SID);
    gOnlineNum--;
    pthread_exit(0);
}

unsigned int getTimeStamp(void)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (tv.tv_sec*1000 + tv.tv_usec/1000);
}

void *thread_Login(void *arg)
{
    int ret, sleep_count;
    DeviceLoginInput s_login;

    memset(&s_login,0,sizeof(s_login));

    s_login.cb = sizeof(DeviceLoginInput);
    s_login.authentication_type = AUTHENTICATE_BY_KEY;
    memcpy(s_login.auth_key,gIotcAuthkey,IOTC_AUTH_KEY_LENGTH);

    PRT_NORMAL("thread_Login start\n");

    while(gProcessRun)
    {
        ret = IOTC_Device_LoginEx((char *)arg,&s_login);
        PRT_DBG(DEF_TAG,"IOTC Device Login ret = %d\n", ret);

        if(ret == IOTC_ER_NoERROR)
        {
            break;
        }
        else
        {
            //PrintErrHandling (ret);
            for(sleep_count = 0 ; sleep_count < 5 && gProcessRun ; sleep_count++)
            {
                sleep(1);
            }
        }
    }

    printf("thread_Login exit\n");
    pthread_exit(0);
}

/********
Thread - Send Audio frames to all AV-idx
*/
void *thread_AudioFrameData(void *arg)
{
    FILE *fp=NULL;
    char buf[AUDIO_BUF_SIZE];
    char g711_buf[AUDIO_BUF_SIZE/2];
    FRAMEINFO_t frameInfo;

    pStSdkRingBufInfoProducer mic_producer = p2p_producer;
    pStSdkRingBufInfo queue = &mic_producer->mic_ring_buffer;
    StSdkRingBufFrame frame;
    frame.frame_buf = buf;

    // *** set audio frame info here ***
    memset(&frameInfo, 0, sizeof(FRAMEINFO_t));
    frameInfo.codec_id = MEDIA_CODEC_AUDIO_G711A;
    frameInfo.flags = (AUDIO_SAMPLE_8K << 2) | (AUDIO_DATABITS_16 << 1) | TUTK_AUDIO_CHANNEL_MONO;

    printf("thread_AudioFrameData start OK\n");
    printf("[Audio] is ENABLED!!\n");
    while(gProcessRun)
    {
        int i;
        int ret;
        int size;

        if (mic_producer->get_one_frame_from_ring_buffer(queue, &frame) < 0)
        {
            usleep(10 * 1000);
            continue;
        }

        frameInfo.timestamp = getTimeStamp();

        for(i = 0 ; i < MAX_CLIENT_NUMBER; i++)
        {
            //get reader lock
            int lock_ret = pthread_rwlock_rdlock(&gClientInfo[i].sLock);
            if(lock_ret)
            {
                printf("Acquire SID %d rdlock error, ret = %d\n", i, lock_ret);
            }

            if(gClientInfo[i].avIndex < 0 || gClientInfo[i].bEnableAudio == 0)
            {
                //release reader lock
                lock_ret = pthread_rwlock_unlock(&gClientInfo[i].sLock);
                if(lock_ret)
                    printf("Acquire SID %d rdlock error, ret = %d\n", i, lock_ret);
                continue;
            }

            size = Ext_InSdkG711A_EnCode(g711_buf, buf, frame.frame_len);
            //PRT_NORMAL("size: %d\n",size);
            // send audio data to av-idx
            ret = avSendAudioData(gClientInfo[i].avIndex, g711_buf, size, &frameInfo, sizeof(FRAMEINFO_t));
            //release reader lock
            lock_ret = pthread_rwlock_unlock(&gClientInfo[i].sLock);
            if(lock_ret)
                printf("Acquire SID %d rdlock error, ret = %d\n", i, lock_ret);

            if(ret == AV_ER_SESSION_CLOSE_BY_REMOTE)
            {
                printf("thread_AudioFrameData: AV_ER_SESSION_CLOSE_BY_REMOTE\n");
                unregedit_client_from_audio(i);
            }
            else if(ret == AV_ER_REMOTE_TIMEOUT_DISCONNECT)
            {
                printf("thread_AudioFrameData: AV_ER_REMOTE_TIMEOUT_DISCONNECT\n");
                unregedit_client_from_audio(i);
            }
            else if(ret == IOTC_ER_INVALID_SID)
            {
                printf("Session cant be used anymore\n");
                unregedit_client_from_audio(i);
            }
            else if(ret == AV_ER_EXCEED_MAX_SIZE)
            {
            }
            else if(ret < 0)
            {
                printf("avSendAudioData error[%d]\n", ret);
                unregedit_client_from_audio(i);
            }
        }
    }

    fclose(fp);

    printf("[thread_AudioFrameData] exit\n");
    pthread_exit(0);
}

/********
Thread - Send Video frames to all AV-idx
*/
void *thread_VideoFrameData(void *arg)
{
    int i = 0, avIndex = 0, bEnableVideo = 0,size = 0, ret = 0, lock_ret = 0;
    int waitKeyFrame = 0;
    char buf[VIDEO_BUF_SIZE];
    FRAMEINFO_t frameInfo;

    pStSdkRingBufInfoProducer v_producer = p2p_producer;
    pStSdkRingBufInfo queue = &v_producer->video_ring_buffer;
    StSdkRingBufFrame frame;
    frame.frame_buf = buf;

    // *** set Video Frame info here ***
    memset(&frameInfo, 0, sizeof(FRAMEINFO_t));
    frameInfo.codec_id = MEDIA_CODEC_VIDEO_HEVC;
    frameInfo.flags = IPC_FRAME_FLAG_IFRAME;

    printf("thread_VideoFrameData start OK\n");
    printf("[Video] is ENABLED!!\n");

    while(gProcessRun)
    {
        if (v_producer->get_one_frame_from_ring_buffer(queue, &frame) < 0)
        {
            usleep(1000);
            continue;
        }

        frameInfo.flags = frame.frame_type;
        frameInfo.timestamp = getTimeStamp();
        size = frame.frame_len;

        for(i = 0 ; i < MAX_CLIENT_NUMBER; i++)
        {
            //get reader lock
            lock_ret = pthread_rwlock_rdlock(&gClientInfo[i].sLock);
            if(lock_ret)
            {
                printf("Acquire SID %d rdlock error, ret = %d\n", i, lock_ret);
            }

            avIndex = gClientInfo[i].avIndex;
            bEnableVideo = gClientInfo[i].bEnableVideo;
            waitKeyFrame = gClientInfo[i].waitKeyFrame;

            //release reader lock
            lock_ret = pthread_rwlock_unlock(&gClientInfo[i].sLock);
            if(lock_ret)
            {
                printf("Acquire SID %d rdlock error, ret = %d\n", i, lock_ret);
            }

            if(avIndex < 0 || bEnableVideo == 0)
            {
                continue;
            }

            if(waitKeyFrame)
            {
                if(frameInfo.flags == IPC_FRAME_FLAG_PBFRAME)
                {
                    Ext_SdkMediaCom_RequestIDR();
                    continue;
                }
                else
                {
                    lock_ret = pthread_rwlock_wrlock(&gClientInfo[i].sLock);
                    if(lock_ret)
                        printf("Acquire SID %d rwlock error, ret = %d\n", i, lock_ret);

                    gClientInfo[i].waitKeyFrame = 0;

                    //release lock
                    lock_ret = pthread_rwlock_unlock(&gClientInfo[i].sLock);
                    if(lock_ret)
                        printf("Release SID %d rwlock error, ret = %d\n", i, lock_ret);
                }
            }

            // Send Video Frame to av-idx and know how many time it takes
            frameInfo.onlineNum = gOnlineNum;

            ret = avSendFrameData(avIndex, buf, size, &frameInfo, sizeof(FRAMEINFO_t));
            if(ret == AV_ER_EXCEED_MAX_SIZE) // means data not write to queue, send too slow, I want to skip it
            {
                usleep(5000);
                continue;
            }
            else if(ret == AV_ER_SESSION_CLOSE_BY_REMOTE)
            {
                printf("thread_VideoFrameData AV_ER_SESSION_CLOSE_BY_REMOTE SID[%d]\n", i);
                unregedit_client_from_video(i);
                unregedit_client_from_command(i);
                continue;
            }
            else if(ret == AV_ER_REMOTE_TIMEOUT_DISCONNECT)
            {
                printf("thread_VideoFrameData AV_ER_REMOTE_TIMEOUT_DISCONNECT SID[%d]\n", i);
                unregedit_client_from_video(i);
                unregedit_client_from_command(i);
                continue;
            }
            else if(ret == IOTC_ER_INVALID_SID)
            {
                printf("Session cant be used anymore\n");
                unregedit_client_from_video(i);
                unregedit_client_from_command(i);
                continue;
            }
            else if(ret < 0)
            {
                printf("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n");
            }
        }
    }

    pthread_exit(0);
}

void InitAVInfo()
{
    int i;
    for(i=0;i<MAX_CLIENT_NUMBER;i++)
    {
        memset(&gClientInfo[i], 0, sizeof(AV_Client));
        gClientInfo[i].avIndex = -1;
        gClientInfo[i].playBackCh = -1;
        pthread_rwlock_init(&(gClientInfo[i].sLock), NULL);
    }
}

void DeInitAVInfo()
{
    int i;
    for(i=0;i<MAX_CLIENT_NUMBER;i++)
    {
        memset(&gClientInfo[i], 0, sizeof(AV_Client));
        gClientInfo[i].avIndex = -1;
        pthread_rwlock_destroy(&gClientInfo[i].sLock);
    }
}

void ProductTest_Streamout_Init()
{
    int ret;

    pthread_t ThreadVideoFrameData_ID;
    pthread_t ThreadAudioFrameData_ID;

    if((ret = pthread_create(&ThreadVideoFrameData_ID, NULL, &thread_VideoFrameData, NULL)))
    {
        printf("pthread_create ret=%d\n", ret);
        exit(-1);
    }
    pthread_detach(ThreadVideoFrameData_ID);

    if((ret = pthread_create(&ThreadAudioFrameData_ID, NULL, &thread_AudioFrameData, NULL)))
    {
        printf("pthread_create ret=%d\n", ret);
        exit(-1);
    }
    pthread_detach(ThreadAudioFrameData_ID);
}

void LoginInfoCB(unsigned int nLoginInfo)
{
    if((nLoginInfo & 0x04))
    {
        printf("I can be connected via Internet\n");
    }
}

int ProductTest_P2P_Main(pStSdkRingBufInfoProducer producer)
{
    int ret , SID;
    pthread_t ThreadLogin_ID;

    PRT_INFO(DEF_TAG,"UID: %s\n", g_TUTKSdk_UID);
    p2p_producer = producer;
    printAccount();

    do
    {
        InitAVInfo();
        ProductTest_Streamout_Init();

        PRT_INFO(DEF_TAG,"new tutk lib\n");
        IOTC_Set_Device_Name("NEW_TUTK");

        ret = TUTK_SDK_Set_License_Key(LICENSE_KEY);
        if (ret != TUTK_ER_NoERROR)
        {
            printf("TUTK_SDK_Set_License_Key()=%d ,exit...!!\n", ret);
            return 0;
        }

        IOTC_Set_Max_Session_Number(MAX_CLIENT_NUMBER);
        // use which Master base on location, port 0 means to get a random port
        ret = IOTC_Initialize2(0);
        if(ret != IOTC_ER_NoERROR)
        {
            printf("[] IOTC_Initialize2(), ret=[%d]\n", ret);
            PrintErrHandling (ret);
            DeInitAVInfo();
            return -1;
        }

        PrintVersion();

        IOTC_Get_Login_Info_ByCallBackFn(LoginInfoCB);
        // alloc MAX_CLIENT_NUMBER*3 for every session av data/speaker/play back
        avInitialize(MAX_CLIENT_NUMBER*3);

        // create thread to login because without WAN still can work on LAN
        if((ret = pthread_create(&ThreadLogin_ID, NULL, &thread_Login, (void *)g_TUTKSdk_UID)))
        {
            printf("Login Thread create fail, ret=[%d]\n", ret);
            return -1;
        }
        pthread_detach(ThreadLogin_ID);

        /**
        if((ret = pthread_create(&Threadpush_ID, NULL, &p2p_push_message_thread, (void *)g_TUTKSdk_UID)))
        {
            printf("push Thread create fail, ret=[%d]\n", ret);
            return -1;
        }
        pthread_detach(Threadpush_ID);
        **/

        gProcessRun = 1;
        while(gProcessRun)
        {
            // Accept connection only when IOTC_Listen() calling
            SID = IOTC_Listen(1000);
            if (SID < 0)
            {
                PrintErrHandling (SID);
                if (SID == IOTC_ER_EXCEED_MAX_SESSION)
                {
                    sleep(5);
                }
                continue;
            }

            int *sid = (int *)malloc(sizeof(int));
            *sid = SID;
            pthread_t Thread_ID;
            ret = pthread_create(&Thread_ID, NULL, &thread_ForAVServerStart, (void *)sid);
            if(ret < 0)
            {
                printf("pthread_create failed ret[%d]\n", ret);
            }
            else
            {
                pthread_detach(Thread_ID);
                gOnlineNum++;
            }
        }
        DeInitAVInfo();
        avDeInitialize();
        IOTC_DeInitialize();
        break;
    } while(1);

    return 0;
}
